import traceback
import inspect
import asyncio

from functools import wraps

from utils.error import *
from utils.log import Log


def parameters_to_string(args, kwargs) -> str:
    kw_list = [f"{key}={str(value)}" for key, value in kwargs.items()]
    return ', '.join([str(arg) for arg in args] + kw_list)


def Handler(code=DefaultError, *, end=False, default_return=None):
    def _function_wrapper(function):
        @wraps(function)
        def _wrapper(*args, **kwargs):
            is_fail = False
            if len(args) and isinstance(args[0], Log):
                self = args[0]
                nargs = args[1:]
            else:
                self = Log()
                nargs = args
            try:
                self.debug(f"method: {function.__module__}::"
                           f"{function.__qualname__}({parameters_to_string(nargs, kwargs)}) enter")
                return function(*args, **kwargs)
            except Exception as e:
                is_fail = True
                if default_return is not None:
                    self.warning(f"method: {function.__module__}::"
                                 f"{function.__qualname__}({parameters_to_string(nargs, kwargs)}) meet err {str(e)}")
                    return default_return
                if isinstance(e, MyError):
                    err = e
                    if e.code == DEFAULT_CODE:
                        err.code = code.get_code()
                        err.msg = code.get_msg()
                else:
                    tb = traceback.format_exc()
                    err = self.error(code.get_code(), code.get_msg(), tb, end=end)
                if end:
                    return err
                else:
                    raise err
            finally:
                if is_fail:
                    if hasattr(self, "rollback"):
                        self.rollback()
                self.debug(f"method: {function.__module__}::"
                           f"{function.__qualname__}({parameters_to_string(nargs, kwargs)}) exit")

        return _wrapper

    return _function_wrapper


def SimpleHandler(end=False):
    def _function_wrapper(function):
        @wraps(function)
        def _wrapper(*args, **kwargs):
            is_fail = False
            if len(args) and isinstance(args[0], Log):
                self = args[0]
                nargs = args[1:]
            else:
                self = Log()
                nargs = args
            try:
                self.debug(f"method: {function.__module__}::"
                           f"{function.__qualname__}({parameters_to_string(nargs, kwargs)}) enter")
                return function(*args, **kwargs)
            except Exception as e:
                is_fail = True
                if isinstance(e, SimpleError):
                    err = e
                else:
                    tb = traceback.format_exc()
                    err = self.simple_error(tb, end=True)
                if end:
                    return err
                else:
                    raise err
            finally:
                if is_fail:
                    if hasattr(self, "rollback"):
                        self.rollback()
                self.debug(f"method: {function.__module__}::"
                           f"{function.__qualname__}({parameters_to_string(nargs, kwargs)}) exit")

        return _wrapper

    return _function_wrapper


def MethodHandler(code=DEFAULT_CODE, msg=DEFAULT_MSG, end=False):
    def _function_wrapper(function):
        @wraps(function)
        def _wrapper(*args, **kwargs):
            is_fail = False
            if len(args) and isinstance(args[0], Log):
                self = args[0]
            else:
                self = Log()
            try:
                self.debug(f"method: {function.__module__}::"
                           f"{function.__qualname__}({parameters_to_string(args, kwargs)}) enter")
                return function(*args, **kwargs)
            except Exception as e:
                is_fail = True
                if isinstance(e, MyError):
                    err = e
                    if e.code == DEFAULT_CODE:
                        err.code = code
                        err.msg = msg
                else:
                    tb = traceback.format_exc()
                    err = self.error(code, msg, tb, end=end)
                if end:
                    return err
                else:
                    raise err
            finally:
                if is_fail:
                    if hasattr(self, "rollback"):
                        self.rollback()
                self.debug(f"method: {function.__module__}::"
                           f"{function.__qualname__}({parameters_to_string(args, kwargs)}) exit")

        return _wrapper

    return _function_wrapper


def AsyncHandler(code=DEFAULT_CODE, msg=DEFAULT_MSG, end=False):
    def wrapper(function):
        @wraps(function)
        async def wrapped(*args, **kwargs):
            is_fail = False
            self = Log()
            if len(args) and isinstance(args[0], Log):
                self = args[0]
            try:
                self.debug(f"async: {function.__module__}::"
                           f"{function.__qualname__}({parameters_to_string(args, kwargs)}) enter")
                if inspect.iscoroutinefunction(function):
                    return await function(*args, **kwargs)
                else:
                    self.error(888, f'need async method for {function.__module__}::{function.__qualname__}')
            except Exception as e:
                is_fail = True
                if isinstance(e, MyError):
                    err = e
                    if e.code == DEFAULT_CODE:
                        err.code = code
                        err.msg = msg
                else:
                    tb = traceback.format_exc()
                    err = self.error(code, msg, tb, end=end)
                if end:
                    return err
                else:
                    raise err
            finally:
                if is_fail:
                    if hasattr(self, "rollback"):
                        self.rollback()
                self.debug(f"async: {function.__module__}::"
                           f"{function.__qualname__}({parameters_to_string(args, kwargs)}) exit")

        return wrapped

    return wrapper


def AsyncHandler2(code=DEFAULT_CODE, msg=DEFAULT_MSG, end=False):
    def wrapper(function):
        @wraps(function)
        async def wrapped(self, *args, **kwargs):
            is_fail = False
            try:
                self.debug(f"async: {function.__module__}::"
                           f"{function.__qualname__}({parameters_to_string(args, kwargs)}) enter")
                if inspect.iscoroutinefunction(function):
                    return await function(self, *args, **kwargs)
                else:
                    loop = asyncio.get_event_loop()
                    return await loop.run_in_executor(None, function, self, *args)
            except Exception as e:
                is_fail = True
                if isinstance(e, MyError):
                    err = e
                    if e.code == DEFAULT_CODE:
                        err.code = code
                        err.msg = msg
                else:
                    tb = traceback.format_exc()
                    err = self.error(code, msg, tb, end=end)
                if end:
                    return err
                else:
                    raise err
            finally:
                if is_fail:
                    if hasattr(self, "rollback"):
                        self.rollback()
                self.debug(f"async: {function.__module__}::"
                           f"{function.__qualname__}({parameters_to_string(args, kwargs)}) exit")

        return wrapped

    return wrapper
